#!/bin/bash
#================================================{ Functions }==========================================================
bye () { cursor on; exit $1; }

cursor () {
    case $1 in
         on) stty  echo; printf "$CON";;
        off) stty -echo; printf "$COF";;
    esac
}

mov () {
    case $1  in -) cuter=++;; +) cuter=--;; esac
    case $SC in
         (0) [[ $OX -le -$OW   ]] && return 1
             [[ $OX -gt  $endx ]] && return 1
             [[ $AS -ge  $AL   ]] && AS=0  || ((AS++)) # sprites animation
             [[ $OX -le  1     ]] && ((CS++))
             ((OX$1$1)); ((CL$cuter)); SC=$SM
             [[ $CL -lt 0 ]] && CL=0;;
         (*) ((SC--));;
    esac
    [[ $OX -le 1 ]] && SX=0 || SX=$OX
}

cut () {
    xcord=$1 ycord=$2 color=$3; shift 3
    screen+="\e[$ycord;${xcord}H$color"
    printf -v spr %s "${@:$CS:$CL}"
    spr="${spr//_Y_/'\e['$ycord;}"
    screen+="${spr//⎕/ }"
}

movy () {
    case $SC:$1 in
          (*:-) [[ $OY -le 1         ]] && return 1;;&
          (*:+) [[ $OY -ge $[endy-2] ]] && return 1;;&
          (0:*) [[ $AS -ge $AL  ]] && AS=0 || ((AS++))
                [[ $CS -le $OH  ]] && ((CS++))
                ((OY$1$1)); SC=$SM;;
          (*:*) ((SC--));;
    esac
}

cuty () { # used in bonus crate
    xcord=$1 ycord=$2 cuter=$3 ypos=$[ycord-cuter]; shift 3
    [[ $ypos -lt 1    ]] && ypos=1
    [[ $CS -ge $cuter ]] && spr="\e[$ypos;${xcord}H$@" screen+="${spr//⎕/ }"
}

bonus () { # get random bonus from alien or not
    case $[RANDOM % $rnd] in
        0) ENM+=("${bonuses[$[RANDOM%${#bonuses[@]}]]} $OX $[OY-1] 3 5 0 2 1 0 0 0 ${#ENM[@]}")
    esac
}

mess () {
    cursor off
    XY 1 1 ${DEF}; clear
    case $1 in
        win ) sprite_win ; XY 0 ${endy}; read -t3 -n1000 trash; the_story; return 0;;
        lose) sprite_lose; XY 0 ${endy}; read -t3 -n1000 trash; bye 1;;
        tobe) sprite_tobe; XY 0 ${endy}; read -t3 -n1000 trash; bye 1;;
        help) sprite_help;;
    esac
}

print_sprite () {
    OT=${OI[0]}   # object type
    OX=${OI[1]}   # X coordinate
    OY=${OI[2]}   # Y coordinate
    OH=${OI[3]}   # object height
    OW=${OI[4]}   # object width
    SC=${OI[5]}   # speed counter
    SM=${OI[6]}   # speed max
    CS=${OI[7]}   # cutting start
    CL=${OI[8]}   # cutting length
    AS=${OI[9]}   # animation start
    AL=${OI[10]}  # animation length
    C1=${OI[11]}  # custom obj parameter 1
    C2=${OI[12]}  # custom obj parameter 2
    [[ $OT ]] && sprite_$OT
}

read_input () { read -t$spd -srn1 input  &>/dev/null ; }
listener   () { input2=$(nc -l -p $sport 2>/dev/null); }
sender     () { until nc -q0 $1 $2 2>/dev/null <<< "$3"; do :; done; }

control_hero1 () {
    read_input
    case $input in
        [wW]) [[ $H1Y  -gt 2               ]] && ((H1Y--));;
        [aA]) [[ $H1X  -gt 1               ]] && ((H1X--));;
        [sS]) [[ $H1Y  -lt $heroendy       ]] && ((H1Y++));;
        [dD]) [[ $H1X  -lt $heroendx       ]] && ((H1X++));;
        [pP]) [[ $ammo -ge $G && $C1 -eq 0 ]] && { C1=7; add_piu $1 $2 1 $G; ((ammo-=$G)); };;
    esac
}

control_hero2 () {
    case $input2 in
        [wW]) [[ $H2Y   -gt 2                ]] && ((H2Y--));;
        [aA]) [[ $H2X   -gt 1                ]] && ((H2X--));;
        [sS]) [[ $H2Y   -lt $heroendy        ]] && ((H2Y++));;
        [dD]) [[ $H2X   -lt $heroendx        ]] && ((H2X++));;
        [pP]) [[ $ammo2 -ge $G2 && $C1 -eq 0 ]] && { C1=7; add_piu $1 $2 2 $G2; ((ammo2-=$G2)); };;
    esac
}

rndmbonus () {
    case $1:$[RANDOM%3] in
          1:0) ((life++)) ;;
          2:0) ((life2++));;
          1:1) ((ammo+=100)) ;;
          2:1) ((ammo2+=100));;
          1:2) [[ $G  -lt 5 ]] && ((G++)) ;;
          2:2) [[ $G2 -lt 5 ]] && ((G2++));;
    esac
}
collbooom () { unset ENM[obj_i]; boom $obj_x $obj_y; }
collision () {
    hero=$1 re="$2"
    case $type in
        'duel') target=("${HER[@]}" "${ENM[@]}");;
             *) target=("${ENM[@]}");;
    esac
    [[ ${target[@]} =~ $re ]] && {
        match=($BASH_REMATCH)
        obj_i=${match[@]:11:1}
        obj_x=${match[@]:1:1}
        obj_y=${match[@]:2:1}
        case $match:$hero in
              bfire:*) collbooom; return 0;;
               boss:*) ((bhealth--)); boom $3 $4; return 0;;
               life:1) collbooom; ((life++))    ; return 1;;
               life:2) collbooom; ((life2++))   ; return 1;;
               ammo:1) collbooom; ((ammo+=100)) ; return 1;;
               ammo:2) collbooom; ((ammo2+=100)); return 1;;
#              alien:*) collbooom; ((enumber--)) ; return 0;;
              gunup:1) collbooom; [[ $G  -lt 5 ]] && ((G++))  ; return 1;;
              gunup:2) collbooom; [[ $G2 -lt 5 ]] && ((G2++)) ; return 1;;
              alien:1) collbooom; ((enumber--)) ; ((frags++)) ; bonus; return 0;;
              alien:2) collbooom; ((enumber--)) ; ((frags2++)); bonus; return 0;;
              hero1:2) ((frags2++)); ((life--)) ; BOOM $obj_x $obj_y ; return 0;;
              hero2:1) ((frags++)) ; ((life2--)); BOOM $obj_x $obj_y ; return 0;;
              crate:*) collbooom; crate=0; boom $obj_x $[obj_y-3]; boom $obj_x $[obj_y-6]; rndmbonus $hero; return 1;;
        esac
    }
}

boom () {
    XB=$1 YB=$2
    [[ $XB -lt     2     ]] && XB=2
    [[ $XB -gt $boomendx ]] && XB=$boomendx
    BP3+=("boom $XB $YB 0 0 0 $[RANDOM%3] 1 9 0 5")
}

BOOM () {  XB=$1   YB=$2
    boom  $XB    $[YB+1]
    boom $[XB+4]  $YB
    boom $[XB+1] $[YB+1]
    boom $[XB+3]  $YB
    boom  $XB    $[YB+1]
}

fps_counter () { #-{ Print FPS }-----------------------------------------------
    [[ $SECONDS -gt $sec ]] && {
        FPS=$FPSC AVG+=($FPS)
        [[ $FPSL ]] || FPSL=$FPS
        [[ $FPS -gt $FPSM  ]] && FPSM=$FPS
        [[ $FPS -lt $FPSL  ]] && FPSL=$FPS
        (( ${#AVG[@]} == 5 )) && { avg="${AVG[@]}"; FPSA=$(( (${avg// /+})/5 )); AVG=(); }
        sec=$SECONDS
        FPSC=0
    } || ((FPSC++))
    case $game in
        'server') screen+="\e[$endy;$[endx-9]H$LND${BLK}FPS: $RED$FPS $DEF";;
               *) screen+="\e[$endy;2H$LND${BLK}FPS: $RED$FPS ${BLK}max: $RED$FPSM ${BLK}low: $RED$FPSL ${BLK}avg: $RED$FPSA $DEF";;
    esac
}

status () { #-{ Print game status }--------------------------------------------
    case $type:$virus in
         (duel:*       ) enemy=rival   bullets=Ammo;;
         (   *:covid-19) enemy=viruses bullets=Meds;;
         (team:*       ) enemy=aliens  bullets=Ammo;;
    esac
    screen+="\e[1;2H$SKY${BLK}Killed $enemy: $SKY$RED$frags$SKY ${BLK}Life: $SKY$RED$life$SKY ${BLK}$bullets: $SKY$RED$ammo$SKY "
    case $game in 'server')
        screen+="\e[$endy;2H$LND${BLK}Killed $enemy: $LND$RED$frags2$LND ${BLK}Life: $LND$RED$life2$LND ${BLK}$bullets: $LND$RED$ammo2$LND ";;
    esac
}

boss_health () {
    bar=; hp=$((bosshbar * bhealth / 100)); hm=$((endx - 10))
    for (( i=0  ; i<hp; i++ )); do bar="█$bar"; done
    for (( i=$hp; i<hm; i++ )); do bar="$bar "; done
    screen+="\e[$((endy-1));1H$LND $BLD${RED}BOSS: $LND$BLK|$BLD$RED$bar$LND$BLK|$LND"
}

fill_screen () {
    for ((i=0;        i<landendy; i++)); do printf "$SKY%${endx}s"; done
    for ((i=landendy; i<endy;     i++)); do printf "$LND%${endx}s"; done
}

drop_crate () { # in duel mode ad plane'll drop bonus crate
    case $type:$crate in duel:0)
        [[ $OX -eq $halfx ]] && { crate=1 ENM+=("crate $halfx 6 7 7 0 $[RANDOM%5+5] 0 0 0 0 ${#ENM[@]}"); };;
    esac
}

add_enm () {
    [[ $BR -gt 0 ]] && ((BR--))
    [[ $enumber -lt $enmax && $BR -eq 0 ]] || return
    ENM+=("alien $1 $2 3 5 0 $[RANDOM%2] 1 ${3:-0} $[RANDOM%3] 3 ${#ENM[@]}")
    ((enumber++))
    BR=20
}

add_piu () {
    PX=$1 PY=$2 PH=$3 PS=$4
    case $PS in
        1) PIU+=("piu$PH $[PX+12] $[PY+3] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3");; # -=>

        2) PIU+=("piu$PH $[PX+12] $[PY+4] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3"    # -=>
                                                                                      #
                 "piu$PH $[PX+12] $[PY+2] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3");; # -=>

        3) PIU+=("piu$PH $[PX+12] $[PY+2] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3"    # -=>
                 "piu$PH $[PX+13] $[PY+3] 1 4 0 0 1 $[endx-(PX+13)] $[RANDOM%3] 3"    #  -=>
                 "piu$PH $[PX+12] $[PY+4] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3");; # -=>

        4) PIU+=("piu$PH $[PX+12] $[PY+1] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3"    # -=>
                 "piu$PH $[PX+13] $[PY+2] 1 4 0 0 1 $[endx-(PX+13)] $[RANDOM%3] 3"    #  -=>
                                                                                      #
                 "piu$PH $[PX+13] $[PY+4] 1 4 0 0 1 $[endx-(PX+13)] $[RANDOM%3] 3"    #  -=>
                 "piu$PH $[PX+12] $[PY+5] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3");; # -=>

        5) PIU+=("piu$PH $[PX+12] $[PY+1] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3"    # -=>
                 "piu$PH $[PX+13] $[PY+2] 1 4 0 0 1 $[endx-(PX+13)] $[RANDOM%3] 3"    #  -=>
                 "piu$PH $[PX+14] $[PY+3] 1 4 0 0 1 $[endx-(PX+14)] $[RANDOM%3] 3"    #   -=>
                 "piu$PH $[PX+13] $[PY+4] 1 4 0 0 1 $[endx-(PX+13)] $[RANDOM%3] 3"    #  -=>
                 "piu$PH $[PX+12] $[PY+5] 1 4 0 0 1 $[endx-(PX+12)] $[RANDOM%3] 3");; # -=>
    esac
}

backgound_birds () { # Add birds }----------------------------------------------------------------------
    case $[RANDOM % $BRD] in 0) BP1+=("bird  $endx $rndy 2 2  0 $[3+RANDOM%3] 1 0 $[RANDOM%2] 2");; esac
}

backgound_msngr () { # Add msngr }----------------------------------------------------------------------
    case $type in duel) msngrY=2;; *) msngrY=$rndy;; esac
    case $[RANDOM % $MSN] in 0) BP2+=("msngr $endx $msngrY 4 20 0 $[1+RANDOM%4] 1 0 $[RANDOM%3] 3");; esac
}

backgound_clouds () { # Add clouds }--------------------------------------------------------------------
    case $[RANDOM % $CLD]:$[RANDOM % 3 + 1] in
        0:1) BP1+=("cloud1 $endx $rndy 3 6  6 $[6+RANDOM%2] 1 0 0 0");;
        0:2) BP2+=("cloud2 $endx $rndy 3 8  4 $[4+RANDOM%2] 1 0 0 0");;
        0:3) BP3+=("cloud3 $endx $rndy 3 11 2 $[2+RANDOM%2] 1 0 0 0");;
    esac
}

backgound_houses () { # Add houses }--------------------------------------------------------------------
    case $[RANDOM % $HOU]:$[RANDOM % 3 + 1] in
        0:1) BP1+=("house1 $endx $hos1endy 2 6  6 $[6+RANDOM%2] 1 0 0 0");;
        0:2) BP2+=("house2 $endx $hos2endy 9 30 4 $[4+RANDOM%2] 1 0 0 0");;
        0:3) BP3+=("house3 $endx $hos3endy 4 17 2 $[2+RANDOM%2] 1 0 0 0");;
    esac
}

backgound_trees () { # Add trees }----------------------------------------------------------------------
    case $[RANDOM % $TRE]:$[RANDOM % 3 + 1] in
        0:1) BP1+=("tree1 $endx $tre1endy 3 2 6 $[6+RANDOM%2] 1 0 0 0");;
        0:2) BP2+=("tree2 $endx $tre2endy 6 5 4 $[4+RANDOM%2] 1 0 0 0");;
        0:3) BP3+=("tree3 $endx $tre3endy 9 9 2 $[2+RANDOM%2] 1 0 0 0");;
    esac
}

backgound_moving_land () { # Print moving land }--------------------------------------------------------
    screen+="\e[$landendy;1H$LND${land:$((1-${LX:-1})):$endx}"; [[ ${LX:-1} -lt -$[$LN/2] ]] && LX=-1 || ((LX--))
}

backgound_land () { # Print land }----------------------------------------------------------------------
    screen+="\e[$landendy;1H$LND${land:$((1-${LX:-1})):$endx}"
}

add_backgound () {
    rndy=$[2+RANDOM%10]; [[ $rndy -gt $[3+enmyendy] ]] && rndy=$[3+enmyendy]
    for item in "$@"; { backgound_$item; }
}

#------------------------{ Intro }--------------------------------------------------------------------------------------
intro () {
    i=0  #    start X                           target X
    INT=("PIU $endx 3 9 41 0 0 1 0          0 0 $[endx/2-35]"
         "PIU $endx 3 9 41 0 0 1 0          0 0 $[endx/2-1]"
         "ARR 1    12 6 28 0 0 0 $[endx-29] 0 0 $[endx/2-16]")
    while true; do
        sleep $spd; screen=
        OI=(${INT[i]})
        print_sprite || ((i++))
        printf "$screen"
        [[ $i -gt ${#INT[@]} ]] && break
    done
    XY $[endx/2+32] 11 "$version"
}

HCI=0 SCI=1 HSI=0 H1C=${H1C:-$DHC} H1S=${H1S:-$DHS} S1C=${S1C:-$DSC}
hero_sign=(★ ☭ ✚ ✙ ✠ ✡ ✦ ✧ ✩ ✪ ☘ ☠ ☢ ☣ ☯ ❃ ❂ ❁ ✿ ❀ ✣ ♣ ♠ ♥ ♦ ❤ ● ☻ ♀ ♂)
hero_color=($BLK $RED $GRN $YLW $BLU $MGN $CYN $WHT)
sign_color=($BLK $RED $GRN $YLW $BLU $MGN $CYN $WHT)
hero_config () {
    screen=
    [[ $AS -gt 0 ]] && ((AS--)) || AS=3
    CM1=$SKY$H1C$BLD CM2=$SKY$DIM$H1C CM3=$SKY$H1C CM4=$SKY$BLD$H1C$UND TR=${tailrotor[AS]} OX=$[$endx/2-9] OY=$[$endy/2-6] CS=1 CL=20
    # Using the same 'cut' function to print hero as in the game to preserve this nicely done tabled structure
    ######X####Y#####CLR  1   2   3       4        5       6       7       8        9       A        B       C       D       E   F   G  #0
    cut $OX  $OY    $SKY ' ' ' ' ' '     ' '      ' '     ' '     ' '     ' '      ' '     ' '      ' '     ' '     ' '     ' ' ' ' ' ' #1
    cut $OX $[OY+1] $CM1 ' ' '_' '_'     ' '                                         ${mainrotor[AS]}                               ' ' #2
    cut $OX $[OY+2] $CM2 ' ' '|' $TR $CM1'\\' $CM1'_' $CM1'_' $CM1'_' $CM3'.'  $CM3'-' $CM3'╨'  $CM4'─' $CM4'｡' $CM1'_' $SKY' ' ' ' ' ' #3
    cut $OX $[OY+3] $CM2 ' ' '`' '─'     '´'      '‾'     '‾'     '‷'     '\\' $blk'°' $S1C$H1S $SKY' ' $blk'º' $blk'¯' $CM1']' '─' ' ' #4
    cut $OX $[OY+4] $CM2 ' ' ' ' ' '     ' '      ' '     ' '     ' '     ' '      '‾'     '‾'      '‾'     '‾'     '‾' $SKY' ' ' ' ' ' #5
    cut $OX $[OY+5] $SKY ' ' ' ' ' '     ' '      ' '     ' '     ' '     ' '      ' '     ' '      ' '     ' '     ' '     ' ' ' ' ' ' #6
    printf "$screen"

    for i in ${!hero_sign[@]}; {
        [[ $i -eq $HSI ]] && selected=$DEF$BLD$RED || selected=$DEF
        [[ $i -lt $[${#hero_sign[@]}/2] ]] \
            && XY $[MX+12+i*2] $MY     "$selected${hero_sign[i]}$DEF" \
            || XY $[MX+12+i*2-${#hero_sign[@]}] $[MY+1] "$selected${hero_sign[i]}$DEF"
    }
    for i in ${!sign_color[@]}; {
        [[ $i -eq $SCI ]] && selected=$BLD || selected=$DEF
        XY $[MX+12+i*2] $[MY+2] "$selected${sign_color[i]}●$DEF"
    }
    for i in ${!hero_color[@]}; {
        [[ $i -eq $HCI ]] && selected=$BLD || selected=$DEF
        XY $[MX+12+i*2] $[MY+3] "$selected${hero_color[i]}●$DEF"
    }
}

submenu () {
    case "${items[item]}$1" in
        'Hero  sign+') [[ $HSI -lt $[${#hero_sign[@]}-1] ]] && ((HSI++));;
        'Hero  sign-') [[ $HSI -gt 0 ]] && ((HSI--));;
        'Sign color+') [[ $SCI -lt $[${#sign_color[@]}-1] ]] && ((SCI++));;
        'Sign color-') [[ $SCI -gt 0 ]] && ((SCI--));;
        'Hero color+') [[ $HCI -lt $[${#hero_color[@]}-1] ]] && ((HCI++));;
        'Hero color-') [[ $HCI -gt 0 ]] && ((HCI--));;
    esac
    H1C=${hero_color[HCI]} H1S=${hero_sign[HSI]} S1C=${sign_color[SCI]}
}

item=1
selector () {
    read -t$spd -srn1 input &> /dev/null
    case $input in
        [wW]) [[ $item -gt 1 ]] && ((item--));;
        [aA]) submenu -;;
        [sS]) [[ $item -lt $[${#items[@]}-1] ]] && ((item++));;
        [dD]) submenu +;;
        [pP]) return $item;;
    esac;     return 0
}

printf -v eraser %40s
clr    () { for i in ${!items[@]}; { XY $MX $[MY+i] "$eraser"; }; }
Hero   () { :; }
Sign   () { :; }
back   () { clr; menu; }
start  () { return  1; }
team   () { type=duel; multiplayer; }
duel   () { type=team; multiplayer; }
server () { game=server; clr; menu; }
single () { game=single; clr; menu; }
cancel () { apply; H1C=$DHC; H1S=$DHS; S1C=$DSC; }
client () { clr; game=client; enter_server; clr; menu; }
apply  () { printf "$DEF"; clear; intro; show_hero=; item=1; items=('' "start $game" 'multiplayer' 'configure' 'exit'); }
configure    () { clear; show_hero=1; items=('' 'Hero  sign' 'Sign color' 'Hero color' 'apply' 'cancel'); }
multiplayer  () { clr; items=('' 'server' 'client' 'single' "$type" 'back'); }
enter_server () {
    XY $[$endx/2-10] $[$endy-4] "${RED}Enter server IP: "
    XY $[$endx/2-10] $[$endy-3] "$BLD$WHT"
    cursor on; read -p ' ' -i "$saddr" -e saddr; cursor off
}

game=single
type=team
menu () {
     item=1 MX=$[$endx/4+4] MY=$[$endy-6]
    items=('' "start $game" 'multiplayer' 'configure' 'exit')
    while true; do
        selector || ${items[$?]// */} || break
        [[ $show_hero ]] && hero_config
        for i in ${!items[@]}; {
            [[ $i -eq $item ]] && selected=$BLD$RED || selected=$DEF
            XY $MX $[MY+i] "$selected${items[i]}$DEF"
        }
    done
}
